package io.capsulode.tunnel.blockrepo;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.net.URI;
import java.util.Set;

@Controller
public class PacController {
    private static final String PAC_CONTENT_TYPE = "application/x-ns-proxy-autoconfig";
    private static final MediaType TEXT_PLAIN = MediaType.parseMediaType("text/plain;charset=UTF-8");

    private final PacRepository repository;
    private final PacConfiguration pacConfiguration;

    public PacController(PacRepository repository, PacConfiguration pacConfiguration) {
        this.repository = repository;
        this.pacConfiguration = pacConfiguration;
    }

    @RequestMapping(value = "/pac", method = RequestMethod.GET)
    public void defaultPac(ServletWebRequest webRequest, HttpServletResponse response) throws IOException {
        String serverName = webRequest.getRequest().getServerName();
        generatePac(serverName, pacConfiguration.getDefaultPort(), pacConfiguration.getDefaultHttpPort(), webRequest, response);
    }

    @RequestMapping(value = "/pac/{host:[^:]+}", method = RequestMethod.GET)
    public void defaultHostPac(@PathVariable String host,
                               ServletWebRequest webRequest, HttpServletResponse response) throws IOException {
        generatePac(host, pacConfiguration.getDefaultPort(), pacConfiguration.getDefaultHttpPort(), webRequest, response);
    }

    @RequestMapping(value = "/pac/{host:[^:]+}:{port:\\d+}", method = RequestMethod.GET)
    public void socks(@PathVariable String host,
                      @PathVariable int port,
                      ServletWebRequest webRequest,
                      HttpServletResponse response) throws IOException {
        generatePac(host, port, pacConfiguration.getDefaultHttpPort(), webRequest, response);
    }


    @RequestMapping(value = "/pac/{host:[^:]+}:{socksPort:\\d+}:{httpPort:\\d+}", method = RequestMethod.GET)
    public void pac(@PathVariable String host,
                    @PathVariable int socksPort,
                    @PathVariable int httpPort,
                    ServletWebRequest webRequest,
                    HttpServletResponse response) throws IOException {
        generatePac(host, socksPort, httpPort, webRequest, response);
    }

    private void generatePac(String host, int socksPort, int httpPort,
                             ServletWebRequest webRequest, HttpServletResponse response) throws IOException {

        if (webRequest.checkNotModified(repository.etag(), repository.lastModified())) {
            return;
        }
        StringBuilder proxy = new StringBuilder();
        if (socksPort > 0) {
            proxy.append("SOCKS5 ").append(host).append(":").append(socksPort).append("; ")
                    .append("SOCKS ").append(host).append(":").append(socksPort).append("; ");
        }
        if (httpPort > 0) {
            proxy.append("PROXY ").append(host).append(":").append(httpPort).append("; ");
        }
        proxy.append("DIRECT;");
        Set<String> blocks = repository.domains();

        response.setContentType(PAC_CONTENT_TYPE);
        response.setCharacterEncoding("UTF-8");
        try (Writer writer = response.getWriter()) {
            writer.append("var proxy = \"").append(proxy.toString()).append("\";\n")
                    .append("var domains = {");
            for (String s : blocks) {
                writer.append("\n    \"").append(s).append("\": 1,");
            }
            writer.append("\n    \"mock.domain.0\": 0");
            writer.append("\n};\n");
            for (String s : repository.getPacEndingLines()) {
                writer.append(s).append('\n');
            }
        }
    }

    @RequestMapping(value = "/domains", method = RequestMethod.GET)
    public void blockedHosts(ServletWebRequest webRequest, HttpServletResponse response) throws IOException {
        if (webRequest.checkNotModified(repository.etag())) {
            return;
        }
        response.setContentType(MediaType.TEXT_PLAIN.toString());
        response.setCharacterEncoding("UTF-8");
        try (Writer writer = response.getWriter()) {
            for (String s : repository.domains()) {
                writer.append(s).append('\n');
            }
        }
    }

    @RequestMapping(value = "/domains/{domain:[^:]+}", method = RequestMethod.POST)
    public ResponseEntity<String> addBlockedHost(@PathVariable String domain) {
        if (domain.indexOf(".") <= 0) {
            return ResponseEntity.unprocessableEntity()
                    .contentType(TEXT_PLAIN)
                    .body("可能错误的域名格式：" + domain);
        }

        if (repository.put(domain)) {
            URI location = ServletUriComponentsBuilder
                    .fromCurrentContextPath()
                    .path("/domains")
                    .buildAndExpand().toUri();
            return ResponseEntity.created(location)
                    .contentType(TEXT_PLAIN)
                    .body("SUCCESS");
        } else {
            return ResponseEntity.status(HttpStatus.ALREADY_REPORTED)
                    .contentType(TEXT_PLAIN)
                    .body("早已包含该域名：" + domain);
        }
    }

    @RequestMapping(value = "/domains/{domain:[^:]+}", method = RequestMethod.DELETE)
    public ResponseEntity<String> deleteHost(@PathVariable String domain) {
        if (repository.remove(domain)) {
            return ResponseEntity.ok()
                    .contentType(TEXT_PLAIN)
                    .body("SUCCESS");
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .contentType(TEXT_PLAIN)
                    .body("未知的域名：" + domain);
        }
    }
    @RequestMapping(value = "/domains/{domain:[^:]+}", method = RequestMethod.GET)
    public ResponseEntity<String> checkHost(@PathVariable String domain) {
        boolean exists = repository.domains().contains(domain);
        if(exists){
            return ResponseEntity.ok()
                    .contentType(TEXT_PLAIN)
                    .body("Already exists!");
        }
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .contentType(TEXT_PLAIN)
                .body("Not exists yet!");
    }
}
